package com.duoec.dw.lineage.core.utils;

import com.duoec.dw.lineage.dto.*;
import com.duoec.dw.lineage.exceptions.UndefinedLineageAnalyserException;
import com.google.common.base.Splitter;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @author xuwenzhen
 */
public class AnalyserUtils {

    public static final char SEMICOLON_STR = '`';
    public static final String STR_JAR = " jar ";

    private AnalyserUtils() {
    }

    /**
     * 从Map中查找对应的分析器，如果没有精准的，则尝试匹配是否是其子类
     *
     * @param obj         当前值
     * @param analyserMap 分析器映射表
     * @param <A>         分析器类型
     * @return 对应的分析器，如果找不到，则抛异常！
     */
    public static <A> A getAnalyser(Object obj, Map analyserMap) {
        Class<?> clazz = obj.getClass();
        A analyser = (A) analyserMap.get(clazz);
        if (analyser != null) {
            return analyser;
        }
        Object[] analyserArray = new Object[2];
        analyserMap.forEach((analyserType, analyserObj) -> {
            Class<?> analyserClass = (Class<?>) analyserType;
            if (analyserClass.isAssignableFrom(clazz)) {
                if (analyserArray[0] != null) {
                    //如果已有一个分析器
                    if (analyserClass.isAssignableFrom((Class<?>) analyserArray[1])) {
                        //当前是父类
                        return;
                    }
                }
                analyserArray[0] = analyserObj;
                analyserArray[1] = analyserType;
            }
        });
        if (analyserArray[0] == null) {
            throw new UndefinedLineageAnalyserException("未实现[" + clazz.getName() + "]的分析器！");
        }
        return (A) analyserArray[0];
    }

    public static LinkedHashSet<TableFieldLineageDto> setTableLineageAllFields(TableInfoDto table, MultiTableLineageDto fromTableLineages) {
        LinkedHashSet<TableFieldLineageDto> fields = new LinkedHashSet<>();
        if (table != null) {
            table.getFields().forEach(field -> {
                addField(table.getDatabaseName(), table.getTableName(), fields, field.getColName());
            });
            return fields;
        }
        LinkedHashSet<TableLineageDto> tables = fromTableLineages.getTables();
        tables.stream()
                .filter(fromTableLineage -> fromTableLineage.getTableName() != null)
                .forEach(fromTableLineage -> {
                    LinkedHashSet<TableFieldDependencyDto> globalDependencies = fromTableLineage.getDependencies();
                    fromTableLineage.getFields().forEach(field -> {
                        TableFieldLineageDto fieldLineage = addField(fromTableLineage.getDatabaseName(), fromTableLineage.getTableName(), fields, field.getColName());
                        if (globalDependencies != null) {
                            fieldLineage.getDependencies().addAll(globalDependencies);
                        }
                    });
                });
        return fields;
    }

    private static TableFieldLineageDto addField(String databaseName, String tableName, LinkedHashSet<TableFieldLineageDto> fields, String fieldName) {
        TableFieldLineageDto fieldLineage = new TableFieldLineageDto();
        fieldLineage.setColName(fieldName);
        fieldLineage.setDependencies(new LinkedHashSet<>());
        fieldLineage.setIndex(fields.size());
        LinkedHashSet<TableFieldDependencyDto> dependencies = new LinkedHashSet<>();
        TableFieldDependencyDto dependencyDto = new TableFieldDependencyDto();
        dependencyDto.setDatabaseName(databaseName);
        dependencyDto.setTableName(tableName);
        dependencyDto.setFieldName(fieldName);
        dependencies.add(dependencyDto);
        fieldLineage.setDependencies(dependencies);
        fields.add(fieldLineage);
        return fieldLineage;
    }


    public static String formatFieldName(String fieldName) {
        if (StringUtils.isEmpty(fieldName)) {
            return fieldName;
        }
        if (fieldName.charAt(0) == SEMICOLON_STR && fieldName.charAt(fieldName.length() - 1) == SEMICOLON_STR) {
            fieldName = fieldName.substring(1, fieldName.length() - 1);
        }
        return fieldName;
    }

    public static LinkedHashSet<TableFieldDependencyDto> getFieldDependencies(TableLineageDto tableLineage, String fieldName) {
        LinkedHashSet<TableFieldDependencyDto> tableDependencies = new LinkedHashSet<>();
        if (tableLineage.getDependencies() != null) {
            tableDependencies.addAll(tableLineage.getDependencies());
        }

        Boolean lateralView = tableLineage.getLateralView();

        TableFieldLineageDto field = tableLineage.getField(fieldName);
        if (field == null) {
            // 没有找到有字段
            if (lateralView == null || !lateralView) {
                TableFieldDependencyDto fieldDependency = new TableFieldDependencyDto();
                fieldDependency.setDatabaseName(tableLineage.getDatabaseName());
                fieldDependency.setTableName(tableLineage.getTableName());
                fieldDependency.setFieldName(fieldName);
                tableDependencies.add(fieldDependency);
            }
        } else {
            LinkedHashSet<TableFieldDependencyDto> fieldDependencies = field.getDependencies();
            if (!CollectionUtils.isEmpty(fieldDependencies) || field.getValuable()) {
                if (fieldDependencies != null) {
                    tableDependencies.addAll(fieldDependencies);
                }
            } else {
                //如果当前字段没有依赖时，添加自己
                TableFieldDependencyDto fieldDependency = new TableFieldDependencyDto();
                fieldDependency.setDatabaseName(tableLineage.getDatabaseName());
                fieldDependency.setTableName(tableLineage.getTableName());
                if (!StringUtils.isEmpty(field.getAlias())) {
                    fieldDependency.setFieldName(field.getAlias());
                } else {
                    fieldDependency.setFieldName(field.getColName());
                }
                tableDependencies.add(fieldDependency);
            }
        }
        return tableDependencies;
    }

    public static void mergeDependency(TableLineageDto tableLineage) {
        LinkedHashSet<TableFieldDependencyDto> dependencies = tableLineage.getDependencies();
        if (dependencies == null) {
            dependencies = new LinkedHashSet<>();
            tableLineage.setDependencies(dependencies);
        }
        LinkedHashSet<TableFieldLineageDto> fields = tableLineage.getFields();
        removeGlobalDependenciesFromFields(dependencies, fields);

        // 尝试合并fields中相同的依赖，抽取成全局依赖
        if (fields == null || fields.isEmpty()) {
            return;
        }

        Optional<TableFieldLineageDto> fieldOpt = fields.stream().findFirst();
        LinkedHashSet<TableFieldDependencyDto> globalDependencies = new LinkedHashSet<>();
        LinkedHashSet<TableFieldDependencyDto> fieldDependencies = fieldOpt.get().getDependencies();
        if (fieldDependencies != null) {
            globalDependencies.addAll(fieldDependencies);
        }
        if (CollectionUtils.isEmpty(globalDependencies)) {
            return;
        }

        Iterator<TableFieldLineageDto> its = fields.iterator();
        while (its.hasNext() && !CollectionUtils.isEmpty(globalDependencies)) {
            TableFieldLineageDto field = its.next();
            if (CollectionUtils.isEmpty(field.getDependencies())) {
                //字段没有依赖，则不需要后面的处理了
                continue;
            }

            List<TableFieldDependencyDto> noGlobalDependencies = globalDependencies
                    .stream()
                    .filter(item -> !field.getDependencies().contains(item))
                    .collect(Collectors.toList());
            if (CollectionUtils.isEmpty(noGlobalDependencies)) {
                continue;
            }
            globalDependencies.removeAll(noGlobalDependencies);
        }
        if (!CollectionUtils.isEmpty(globalDependencies)) {
            dependencies.addAll(globalDependencies);
            removeGlobalDependenciesFromFields(globalDependencies, fields);
        }
    }

    public static Set<String> analyseVars(String script) {
        List<String> lines = Lists.newArrayList(Splitter.on(ScriptFormatUtils.TURN_LINE).split(script).iterator());
        Set<String> vars = Sets.newHashSet();
        lines.forEach(line -> {
            Set<String> vs = getVars(line);
            if (CollectionUtils.isEmpty(vs)) {
                return;
            }
            vars.addAll(vs);
        });
        return vars;
    }

    /**
     * add jar hdfs:///edw/util/hive-hcatalog-core-2.3.2.jar;
     * add jar   hdfs:///tmp/hive-hcatalog-core.jar;
     *
     * @param script
     * @return
     */
    public static Set<String> analyseJars(String script) {
        Set<String> jars = Sets.newHashSet();
        int start = 0;
        while (true) {
            int index = script.indexOf(STR_JAR, start);
            if (index == -1) {
                break;
            }

            //检查之前有没有被注释掉
            if (!isComment(script, start, index)) {
                if (isAddJar(script, start, index)) {
                    String jar = getJar(script, index + STR_JAR.length());
                    if (!StringUtils.isEmpty(jar)) {
                        jars.add(jar);
                    }
                }
            } else {
                //如果是注释时，需要一直找到下一个换行
                start = findNextTurnLine(script, index);
                continue;
            }
            start = index + STR_JAR.length();
        }

        return jars;
    }

    private static int findNextTurnLine(String script, int index) {
        for (int i = index; i < script.length(); i++) {
            char c = script.charAt(i);
            if (c == '\n') {
                return i;
            }
        }
        return script.length();
    }

    private static String getJar(String script, int start) {
        int s = start;
        for (int i = start; i < script.length(); i++) {
            char c = script.charAt(i);
            if (s == start) {
                //还没有开始
                if (c == ' ' || c == '\t') {
                    continue;
                } else {
                    s = i - 1;
                }
            }

            if (c == ';' || c == '\n' || c == ' ' || c == '\t') {
                //结束
                String jar = script.substring(s, i);
                if (!jar.endsWith(".jar")) {
                    return null;
                }
                return jar;
            }
        }
        return null;
    }

    private static boolean isAddJar(String script, int start, int end) {
        for (int i = end; i >= start; i--) {
            char c = script.charAt(i);
            if (c == ' ' || c == '\t') {
                continue;
            }
            if (c == 'd' && i - 2 >= start && script.charAt(i - 1) == 'd' && script.charAt(i - 2) == 'a') {
                if (i - 3 >= start) {
                    char c3 = script.charAt(i - 3);
                    if (c3 != '\n' && c3 != ' ' && c3 != '\t' && c3 != ';') {
                        continue;

                    }
                }
                return true;
            }
        }
        return false;
    }

    private static boolean isComment(String script, int start, int end) {
        for (int i = end; i >= start; i--) {
            char c = script.charAt(i);
            if (c == '\n') {
                //中止
                break;
            }
            if (c == '-' && i - 1 >= start && script.charAt(i - 1) == '-') {
                return true;
            }
        }
        return false;
    }

    private static Set<String> getVars(String line) {
        line = line.trim();
        int len = line.length();
        if (len == 0) {
            return null;
        }
        if (line.startsWith(ScriptFormatUtils.COMMENT_LINE)) {
            return null;
        }

        Set<String> vars = null;
        boolean inVar = false;
        int varStart = -1;
        for (int i = 0; i < len; i++) {
            char c = line.charAt(i);
            if (c == '$' && line.charAt(i + 1) == '{' && i < len - 2) {
                varStart = i + 2;
                if (!inVar) {
                    inVar = true;
                }
            } else if (c == '}' && inVar) {
                //结束
                String var = line.substring(varStart, i);
                if (vars == null) {
                    vars = Sets.newHashSet();
                }
                vars.add(var);
                varStart = -1;
                inVar = false;
            }
        }
        return vars;
    }

    public static void fixFieldInfo(TableLineageDto dto) {
        LinkedHashSet<TableFieldDependencyDto> globalDependencies = dto.getDependencies();
        if (globalDependencies != null) {
            globalDependencies.forEach(dependency -> fixDependency(dependency, dto));
        }
        LinkedHashSet<TableFieldLineageDto> fields = dto.getFields();
        if (fields != null) {
            fields.forEach(field -> {
                LinkedHashSet<TableFieldDependencyDto> dependencies = field.getDependencies();
                if (dependencies == null) {
                    return;
                }
                dependencies.forEach(dependency -> fixDependency(dependency, dto));
            });
        }
    }

    public static LinkedHashSet<TableFieldDependencyDto> mergeDependencies(LinkedHashSet<TableFieldDependencyDto> dependencies1, LinkedHashSet<TableFieldDependencyDto> dependencies2) {
        if (dependencies1 == null) {
            return dependencies2;
        }
        if (dependencies2 == null) {
            return dependencies1;
        }
        dependencies1.addAll(dependencies2);
        return dependencies1;
    }

    public static void mergeToDependencies(LinkedHashSet<TableFieldDependencyDto> from, LinkedHashSet<TableFieldDependencyDto> to) {
        if (to == null) {
            return;
        }
        from.addAll(to);
    }

    private static void fixDependency(TableFieldDependencyDto dependency, TableCoordinateDto dto) {
        if (!StringUtils.isEmpty(dependency.getTableName())) {
            return;
        }
        dependency.setDatabaseName(dto.getDatabaseName());
        dependency.setTableName(dto.getTableName());
    }

    private static void removeGlobalDependenciesFromFields(LinkedHashSet<TableFieldDependencyDto> dependencies, LinkedHashSet<TableFieldLineageDto> fields) {
        if (dependencies == null || dependencies.isEmpty()) {
            return;
        }
        if (fields == null || fields.isEmpty()) {
            return;
        }

        //如果存在全局的依赖时，尝试将字段的重复依赖去掉
        fields.stream().filter(field -> field.getDependencies() != null).forEach(field -> {
            List<TableFieldDependencyDto> deletedList = field.getDependencies()
                    .stream()
                    .filter(dependencies::contains)
                    .collect(Collectors.toList());
            if (!deletedList.isEmpty()) {
                field.getDependencies().removeAll(deletedList);
            }
        });

    }
}
